package service

import (
	"ChdChat-Gin/initialize"
	"ChdChat-Gin/model"
	"ChdChat-Gin/service/utils"
	"database/sql"
	"encoding/json"
	"errors"
	"fmt"
	"github.com/gomodule/redigo/redis"
	"github.com/gorilla/websocket"
	"log"
	"sync"
	"time"
)

var (
	ErrGroupMsg   = errors.New("failed to fetch chat group message list")
	ErrPrivateMsg = errors.New("failed to fetch private message list")
)

type Client struct {
	Type       int
	ID         string
	Name       string
	IpAddress  string
	Start      time.Time
	ExpireTime time.Duration
	Socket     *websocket.Conn
	Sender     chan model.ChatRoomMessage
	Receiver   chan model.ChatRoomMessage
}

type Manager struct {
	Login     chan *Client       // 用户登陆
	LogOut    chan *Client       // 用户退出
	Clients   map[string]*Client // 记录在线用户
	ChatGroup map[string][]string
	BroadCast chan model.ChatRoomMessage //消息广播
}

var ChatRoomManager *Manager

func OpenChatRoom() {
	go ChatRoomManager.Quit()          // Listening for client logout
	go ChatRoomManager.Start()         // Listening for client login
	go ChatRoomManager.BroadCastSend() // Listening broadCast message
}

func InitChatRoomManager() error {
	var (
		groupList []model.Contacts
		groupMap  = make(map[string][]string)
	)
	if db := initialize.Conn.Model(model.Contacts{}).
		Where("type = ?", 2).
		Find(&groupList); db.Error != nil {
		return fmt.Errorf(`failed to fetch groups%v`, db.Error)
	}
	for _, item := range groupList {
		groupMap[item.Contact] = append(groupMap[item.Contact], item.User)
	}
	ChatRoomManager = &Manager{
		Login:     make(chan *Client, 64),
		LogOut:    make(chan *Client, 64),
		Clients:   make(map[string]*Client),
		ChatGroup: groupMap,
		BroadCast: make(chan model.ChatRoomMessage, 64),
	}
	return nil
}

func (client *Client) Read() {
	defer func() {
		// When an error occurs, the error message is sent to the client by system
		if err := recover(); err != nil {
			resp := model.ChatRoomMessage{
				Sender:   "system",
				Data:     "エロ発生",
				Receiver: client.ID,
				Type:     model.SYSTEM,
				Date:     time.Now().Format("2006-01-02 15:04:05"),
			}
			log.Printf("chatRoomService.Read:%v\n", err)
			client.Receiver <- resp
		}
	}()
	var msg model.ChatRoomMessage
	for {
		_, byteArr, err := client.Socket.ReadMessage()
		if err != nil {
			panic(err)
		}
		if err = json.Unmarshal(byteArr, &msg); err != nil {
			panic(err)
		}
		// resetting start time
		client.Start = time.Now()
		switch msg.Type {
		case model.SYSTEM:
			{
				if _, ok := ChatRoomManager.Clients[msg.Receiver]; ok == true {
					ChatRoomManager.Clients[msg.Receiver].Receiver <- msg
				}
			}
		case model.BROADCAST:
			{
				ChatRoomManager.BroadCast <- msg
			}
		case model.PRIVATE:
			{
				// client is online? true: send message, false: save message to database
				if _, ok := ChatRoomManager.Clients[msg.Receiver]; ok {
					ChatRoomManager.Clients[msg.Receiver].Receiver <- msg
				} else {
					resp, err := json.Marshal(msg)
					if err != nil {
						panic(err)
					}
					if err = utils.SaveByteArr(msg.Receiver, resp); err != nil {
						panic(err)
					}
				}
				client.Sender <- msg
			}
		case model.GROUPMESSAGE:
			{
				// Save group chat messages separately to the group message list
				resp, err := json.Marshal(msg)
				if err != nil {
					panic(err)
				}
				if err := utils.SaveByteArr(msg.Receiver, resp); err != nil {
					panic(err)
				}
				// Traverse group members and send messages to all members
				for _, member := range ChatRoomManager.ChatGroup[msg.Receiver] {
					if _, ok := ChatRoomManager.Clients[member]; ok {
						ChatRoomManager.Clients[member].Receiver <- msg
					}
				}
			}
		case model.GAMEROOMMESSAGE:
			data := msg.Data.(struct {
				roomId int
				card   int
			})
			room := GameRoomManager.Rooms[data.roomId] // 游戏房间
			player := room.Member[client.ID]           // 玩家
			cardIndex := 0
			for i, v := range player.card {
				if v == data.card {
					cardIndex = i
				}
			}
			player.card = append(player.card[cardIndex:], player.card[:cardIndex+1]...) // 去除打掉的手牌
			room.UsedCard = append(room.UsedCard, data.card)
			room.BroadCast <- msg
		}
	}
}

func (client *Client) Write() {
	defer func() {
		if err := recover(); err != nil {
			resp := model.ChatRoomMessage{
				Sender:   "system",
				Data:     "エロ発生",
				Receiver: client.ID,
				Type:     model.SYSTEM,
				Date:     time.Now().Format("2006-01-02 15:04:05"),
			}
			log.Printf("chatRoomService.Write:%v\n", err)
			client.Receiver <- resp
		}
	}()
	for {
		// When receiving or sending a message, store the message in Redis and write it to websocket
		select {
		case msg := <-client.Sender:
			if err := writeMsg(msg, client); err != nil {
				panic(err)
			}
		case msg := <-client.Receiver:
			if err := writeMsg(msg, client); err != nil {
				panic(err)
			}
		}
	}
}
func (client *Client) Check() {
	defer func() {
		if err := recover(); err != nil {
			resp := model.ChatRoomMessage{
				Sender:   "system",
				Data:     "エロ発生",
				Receiver: client.ID,
				Type:     model.SYSTEM,
				Date:     time.Now().Format("2006-01-02 15:04:05"),
			}
			log.Printf("chatRoomService.Check:%v\n", err)
			client.Receiver <- resp
		}
	}()
	// listen to chat room clients' connection and delete them when the connection expires
	for {
		duration := time.Now().Sub(client.Start)
		if duration >= client.ExpireTime {
			if err := ChatRoomManager.Clients[client.ID].Socket.Close(); err != nil {
				panic(err)
			}
			ChatRoomManager.LogOut <- client
		}
	}
}
func (manager *Manager) Start() {
	var mu = sync.Mutex{}
	// customer enters the chat room, managing it with ChatRoomManager and broadcasts that the customer has been online
	for {
		select {
		case conn, _ := <-manager.Login:
			mu.Lock()
			manager.Clients[conn.ID] = conn
			mu.Unlock()
			go conn.Read()
			go conn.Check()
			go conn.Write()
			respR := model.ChatRoomMessage{
				Receiver: conn.ID,
				Sender:   "system",
				Type:     model.SYSTEM,
				Data:     "welcome back " + conn.Name,
				Date:     time.Now().Format("2006-01-02 15:04:05"),
			}
			conn.Receiver <- respR
			respB := model.ChatRoomMessage{
				Receiver: conn.ID,
				Sender:   "system",
				Type:     model.SYSTEM,
				Data:     conn.Name + "has been online",
				Date:     time.Now().Format("2006-01-02 15:04:05"),
			}
			manager.BroadCast <- respB
		}
	}
}

func (manager *Manager) Quit() {
	var mu sync.Mutex
	for {
		select {
		case conn, _ := <-manager.LogOut:
			mu.Lock()
			delete(manager.Clients, conn.ID)
			mu.Unlock()
			resp := model.ChatRoomMessage{
				Receiver: conn.ID,
				Sender:   "system",
				Type:     model.SYSTEM,
				Data:     conn.Name + " has been offline",
				Date:     time.Now().Format("2006-01-02 15:04:05"),
			}
			manager.BroadCast <- resp
		}
	}
}

func (manager *Manager) BroadCastSend() {
	for {
		select {
		case msg, _ := <-manager.BroadCast:
			for _, conn := range manager.Clients {
				conn.Receiver <- msg
			}
		}
	}
}

func QuitChatRoom(account string) error {
	err := ChatRoomManager.Clients[account].Socket.Close()
	ChatRoomManager.LogOut <- ChatRoomManager.Clients[account]
	return err
}

func writeMsg(msg model.ChatRoomMessage, client *Client) error {
	resp, err := json.Marshal(msg)
	if err != nil {
		return fmt.Errorf("json.Marshal:%v\n", err)
	}
	// client message list only save private message
	// Group chat messages should not save to the client message list
	if msg.Type == model.PRIVATE {
		if err = utils.SaveByteArr(client.ID, resp); err != nil {
			return fmt.Errorf("utils.SaveByteArr:%v\n", err)
		}
	}
	if err = client.Socket.WriteMessage(websocket.TextMessage, resp); err != nil {
		return fmt.Errorf("Socket.WriteMessage:%v\n", err)
	}
	return nil
}

func FetchChatHistory(account string, start, end int) (map[string][]model.ChatRoomMessage, error) {
	var (
		privateChatHistory []model.ChatRoomMessage
		resList            = make(map[string][]model.ChatRoomMessage)
		group              string
		groups             *sql.Rows
		err                error
		historyItem        model.ChatRoomMessage
	)
	redisConn := initialize.RedisPool.Get()
	defer func(redisConn redis.Conn) {
		if err := redisConn.Close(); err != nil {
			panic(err)
		}
	}(redisConn)
	// Query the client's groups which use to fetch client's group message list
	groups, err = initialize.Conn.
		Model(model.Contacts{}).
		Select("contact").
		Where("user = ? and type = 2", account).
		Rows()
	if err != nil {
		log.Printf("db.Rows:%v\n", err)
		return nil, ErrGroupMsg
	}
	// Fetch the group message list from Redis based on the group ID
	for groups.Next() {
		if err = groups.Scan(&group); err != nil {
			log.Printf("sql.Scan:%v\n", err)
			return nil, ErrGroupMsg
		}
		groupMsg, err := redis.Strings(redisConn.Do("LRANGE", group, start, end))
		if err != nil {
			log.Printf("redis.Strings:%v\n", err)
			return nil, ErrGroupMsg
		}
		groupChatHistory := make([]model.ChatRoomMessage, 0, len(groupMsg))
		for _, item := range groupMsg {
			if err = json.Unmarshal([]byte(item), &historyItem); err != nil {
				log.Printf("json.Unmarshal:%v\n", err)
				return nil, ErrGroupMsg
			}
			groupChatHistory = append(groupChatHistory, historyItem)
		}
		resList[group] = groupChatHistory
	}
	// Fetch the private message list from Redis based on the client ID
	privateMsg, err := redis.Strings(redisConn.Do("LRANGE", account, start, end))
	if err != nil {
		log.Printf("redis.Strings:%v\n", err)
		return nil, ErrPrivateMsg
	}
	// Unmarshal chat history []byte
	privateChatHistory = make([]model.ChatRoomMessage, 0, len(privateMsg))
	for _, item := range privateMsg {
		if err = json.Unmarshal([]byte(item), &historyItem); err != nil {
			log.Printf("json.Unmarshal:%v\n", err)
			return nil, ErrPrivateMsg
		}
		privateChatHistory = append(privateChatHistory, historyItem)
	}

	// Group chat history based on contact
	for _, item := range privateChatHistory {
		// let contact = item.Receiver
		// If the recipient is the client itself, the contact is item.Sender
		contactId := item.Receiver
		if contactId == account {
			contactId = item.Sender
		}
		if _, ok := resList[contactId]; ok == false {
			var receiverList []model.ChatRoomMessage
			for _, elem := range privateChatHistory {
				if elem.Sender == contactId || elem.Receiver == contactId {
					receiverList = append(receiverList, elem)
				}
			}
			resList[contactId] = receiverList
		}
	}
	return resList, nil
}

func DeleteChatHistory(user, contact string) (int64, error) {
	return 0, nil
}
